package min3d.core;

import java.util.ArrayList;

import javax.microedition.khronos.opengles.GL10;

import min3d.interfaces.IObject3dContainer;
import min3d.vos.Color4;
import min3d.vos.Number3d;
import min3d.vos.RenderType;
import min3d.vos.ShadeModel;

/**
 * @author Lee
 */
public class Object3d {
	private String					_name;

	private RenderType				_renderType				= RenderType.TRIANGLES;

	private boolean					_isVisible				= true;
	private boolean					_vertexColorsEnabled	= true;
	private boolean					_doubleSidedEnabled		= false;
	private boolean					_texturesEnabled		= true;
	private boolean					_normalsEnabled			= true;
	private boolean					_ignoreFaces			= false;
	private boolean					_colorMaterialEnabled	= false;
	private boolean					_lightingEnabled		= true;

	private Number3d				_position				= new Number3d(0, 0, 0);
	private Number3d				_rotation				= new Number3d(0, 0, 0);
	private Number3d				_scale					= new Number3d(1, 1, 1);

	private Color4					_defaultColor			= new Color4();

	private ShadeModel				_shadeModel				= ShadeModel.SMOOTH;
	private float					_pointSize				= 3f;
	private boolean					_pointSmoothing			= true;
	private float					_lineWidth				= 1f;
	private boolean					_lineSmoothing			= false;

	protected ArrayList<Object3d>	_children;

	protected Vertices				_vertices;
	protected TextureList			_textures;
	protected FacesBufferedList		_faces;

	protected boolean				_animationEnabled		= false;

	private Scene					_scene;
	private IObject3dContainer		_parent;

	/**
	 * Maximum number of vertices and faces must be specified at instantiation.
	 */
	public Object3d(int $maxVertices, int $maxFaces) {
		_vertices = new Vertices($maxVertices, true, true, true);
		_faces = new FacesBufferedList($maxFaces);
		_textures = new TextureList();
	}

	/**
	 * Adds three arguments
	 */
	public Object3d(int $maxVertices, int $maxFaces, Boolean $useUvs, Boolean $useNormals, Boolean $useVertexColors) {
		_vertices = new Vertices($maxVertices, $useUvs, $useNormals, $useVertexColors);
		_faces = new FacesBufferedList($maxFaces);
		_textures = new TextureList();
	}

	/**
	 * This constructor is convenient for cloning purposes
	 */
	public Object3d(Vertices $vertices, FacesBufferedList $faces, TextureList $textures) {
		_vertices = $vertices;
		_faces = $faces;
		if ($textures != null)
		{
			_textures = $textures;
		}
		else
		{
			_textures = new TextureList();
		}
	}

	/**
	 * Holds references to vertex position list, vertex u/v mappings list, vertex normals list, and vertex colors list
	 */
	public Vertices vertices() {
		return _vertices;
	}

	/**
	 * List of object's faces (ie, index buffer)
	 */
	public FacesBufferedList faces() {
		return _faces;
	}

	public TextureList textures() {
		return _textures;
	}

	/**
	 * Determines if object will be rendered. Default is true.
	 */
	public boolean isVisible() {
		return _isVisible;
	}

	public void isVisible(Boolean $b) {
		_isVisible = $b;
	}

	/**
	 * Determines if backfaces will be rendered (ie, doublesided = true). Default is false.
	 */
	public boolean doubleSidedEnabled() {
		return _doubleSidedEnabled;
	}

	public void doubleSidedEnabled(boolean $b) {
		_doubleSidedEnabled = $b;
	}

	/**
	 * Determines if object uses GL_COLOR_MATERIAL or not. Default is false.
	 */
	public boolean colorMaterialEnabled() {
		return _colorMaterialEnabled;
	}

	public boolean lightingEnabled() {
		return _lightingEnabled;
	}

	public void lightingEnabled(boolean $b) {
		this._lightingEnabled = $b;
	}

	public void colorMaterialEnabled(boolean $b) {
		_colorMaterialEnabled = $b;
	}

	/**
	 * Determines whether animation is enabled or not. If it is enabled then this should be an AnimationObject3d instance. This is part of the Object3d class so there's no need to cast anything during the render loop when it's not necessary.
	 */
	public boolean animationEnabled() {
		return _animationEnabled;
	}

	public void animationEnabled(boolean $b) {
		_animationEnabled = $b;
	}

	/**
	 * Determines if per-vertex colors will be using for rendering object. If false, defaultColor property will dictate object color. If object has no per-vertex color information, setting is ignored. Default is true.
	 */
	public boolean vertexColorsEnabled() {
		return _vertexColorsEnabled;
	}

	public void vertexColorsEnabled(Boolean $b) {
		_vertexColorsEnabled = $b;
	}

	/**
	 * Determines if textures (if any) will used for rendering object. Default is true.
	 */
	public boolean texturesEnabled() {
		return _texturesEnabled;
	}

	public void texturesEnabled(Boolean $b) {
		_texturesEnabled = $b;
	}

	/**
	 * Determines if object will be rendered using vertex light normals. If false, no lighting is used on object for rendering. Default is true.
	 */
	public boolean normalsEnabled() {
		return _normalsEnabled;
	}

	public void normalsEnabled(boolean $b) {
		_normalsEnabled = $b;
	}

	/**
	 * When true, Renderer draws using vertex points list, rather than faces list. (ie, using glDrawArrays instead of glDrawElements) Default is false.
	 */
	public boolean ignoreFaces() {
		return _ignoreFaces;
	}

	public void ignoreFaces(boolean $b) {
		_ignoreFaces = $b;
	}

	/**
	 * Options are: TRIANGLES, LINES, and POINTS Default is TRIANGLES.
	 */
	public RenderType renderType() {
		return _renderType;
	}

	public void renderType(RenderType $type) {
		_renderType = $type;
	}

	/**
	 * Possible values are ShadeModel.SMOOTH and ShadeModel.FLAT. Default is ShadeModel.SMOOTH.
	 * 
	 * @return
	 */
	public ShadeModel shadeModel() {
		return _shadeModel;
	}

	public void shadeModel(ShadeModel $shadeModel) {
		_shadeModel = $shadeModel;
	}

	/**
	 * Convenience 'pass-thru' method
	 */
	public Number3dBufferList points() {
		return _vertices.points();
	}

	/**
	 * Convenience 'pass-thru' method
	 */
	public UvBufferList uvs() {
		return _vertices.uvs();
	}

	/**
	 * Convenience 'pass-thru' method
	 */
	public Number3dBufferList normals() {
		return _vertices.normals();
	}

	/**
	 * Convenience 'pass-thru' method
	 */
	public Color4BufferList colors() {
		return _vertices.colors();
	}

	/**
	 * Convenience 'pass-thru' method
	 */
	public boolean hasUvs() {
		return _vertices.hasUvs();
	}

	/**
	 * Convenience 'pass-thru' method
	 */
	public boolean hasNormals() {
		return _vertices.hasNormals();
	}

	/**
	 * Convenience 'pass-thru' method
	 */
	public boolean hasVertexColors() {
		return _vertices.hasColors();
	}

	/**
	 * Clear object for garbage collection.
	 */
	public void clear() {
		if (this.vertices().points() != null)
			this.vertices().points().clear();
		if (this.vertices().uvs() != null)
			this.vertices().uvs().clear();
		if (this.vertices().normals() != null)
			this.vertices().normals().clear();
		if (this.vertices().colors() != null)
			this.vertices().colors().clear();
		if (_textures != null)
			_textures.clear();

		if (this.parent() != null)
			this.parent().removeChild(this);
	}

	//

	/**
	 * Color used to render object, but only when colorsEnabled is false.
	 */
	public Color4 defaultColor() {
		return _defaultColor;
	}

	public void defaultColor(Color4 color) {
		_defaultColor = color;
	}

	/**
	 * X/Y/Z position of object.
	 */
	public Number3d position() {
		return _position;
	}

	/**
	 * X/Y/Z euler rotation of object, using Euler angles. Units should be in degrees, to match OpenGL usage.
	 */
	public Number3d rotation() {
		return _rotation;
	}

	/**
	 * X/Y/Z scale of object.
	 */
	public Number3d scale() {
		return _scale;
	}

	/**
	 * Point size (applicable when renderType is POINT) Default is 3.
	 */
	public float pointSize() {
		return _pointSize;
	}

	public void pointSize(float $n) {
		_pointSize = $n;
	}

	/**
	 * Point smoothing (anti-aliasing), applicable when renderType is POINT. When true, points look like circles rather than squares. Default is true.
	 */
	public boolean pointSmoothing() {
		return _pointSmoothing;
	}

	public void pointSmoothing(boolean $b) {
		_pointSmoothing = $b;
	}

	/**
	 * Line width (applicable when renderType is LINE) Default is 1.
	 * 
	 * Remember that maximum line width is OpenGL-implementation specific, and varies depending on whether lineSmoothing is enabled or not. Eg, on Nexus One, lineWidth can range from 1 to 8 without smoothing, and can only be 1f with smoothing.
	 */
	public float lineWidth() {
		return _lineWidth;
	}

	public void lineWidth(float $n) {
		_lineWidth = $n;
	}

	/**
	 * Line smoothing (anti-aliasing), applicable when renderType is LINE Default is false.
	 */
	public boolean lineSmoothing() {
		return _lineSmoothing;
	}

	public void lineSmoothing(boolean $b) {
		_lineSmoothing = $b;
	}

	/**
	 * Convenience property
	 */
	public String name() {
		return _name;
	}

	public void name(String $s) {
		_name = $s;
	}

	public IObject3dContainer parent() {
		return _parent;
	}

	//

	void parent(IObject3dContainer $container) /* package-private */
	{
		_parent = $container;
	}

	/**
	 * Called by Scene
	 */
	void scene(Scene $scene) /* package-private */
	{
		_scene = $scene;
	}

	/**
	 * Called by DisplayObjectContainer
	 */
	Scene scene() /* package-private */
	{
		return _scene;
	}

	/**
	 * Can be overridden to create custom draw routines on a per-object basis, rather than using Renderer's built-in draw routine.
	 * 
	 * If overridden, return true instead of false.
	 */
	public Boolean customRenderer(GL10 gl) {
		return false;
	}

	public Object3d clone() {
		Vertices v = _vertices.clone();
		FacesBufferedList f = _faces.clone();

		Object3d clone = new Object3d(v, f, _textures);

		clone.position().x = position().x;
		clone.position().y = position().y;
		clone.position().z = position().z;

		clone.rotation().x = rotation().x;
		clone.rotation().y = rotation().y;
		clone.rotation().z = rotation().z;

		clone.scale().x = scale().x;
		clone.scale().y = scale().y;
		clone.scale().z = scale().z;

		return clone;
	}
}
